package git

import (
	"context"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"

	"xindl/deployer/pkg/errors"
	"xindl/deployer/pkg/logger"
)

const (
	// 工作空间目录
	workspaceDir = ".deployer/workspace"
)

// Git客户端
type Client struct {
	log       *logger.Logger
	workDir   string
	service   string
	sshConfig string
}

// 创建新的Git客户端
func NewClient(service string, debug bool) *Client {
	return &Client{
		log:     logger.New(debug).WithPrefix("Git"),
		service: service,
	}
}

// 获取工作目录
func (c *Client) WorkDir() string {
	return c.workDir
}

// 准备工作目录
func (c *Client) prepareWorkDir() error {
	// 创建基础工作目录
	baseDir := filepath.Join(workspaceDir, c.service)
	absBaseDir, err := filepath.Abs(baseDir)
	if err != nil {
		return errors.NewErrorWithCode(errors.ErrCodeIO, "无法获取绝对路径").
			WithContext("dir", baseDir)
	}

	// 如果目录已存在，则删除
	if _, err := os.Stat(absBaseDir); err == nil {
		c.log.Debug("清理已存在的工作目录: %s", absBaseDir)
		if err := os.RemoveAll(absBaseDir); err != nil {
			return errors.NewErrorWithCode(errors.ErrCodeIO, "清理工作目录失败").
				WithContext("dir", absBaseDir)
		}
	}

	// 创建新的工作目录
	c.log.Debug("创建工作目录: %s", absBaseDir)
	if err := os.MkdirAll(absBaseDir, 0755); err != nil {
		return errors.NewErrorWithCode(errors.ErrCodeIO, "创建工作目录失败").
			WithContext("dir", absBaseDir)
	}

	c.workDir = absBaseDir
	return nil
}

// 配置SSH
func (c *Client) configureSSH() error {
	home, err := os.UserHomeDir()
	if err != nil {
		return errors.NewErrorWithCode(errors.ErrCodeSystem, "无法获取用户主目录")
	}

	// 检查SSH密钥
	sshKeyPath := filepath.Join(home, ".ssh", "id_rsa")
	if _, err := os.Stat(sshKeyPath); os.IsNotExist(err) {
		return errors.NewErrorWithCode(errors.ErrCodeGitAuth, "SSH私钥不存在").
			WithContext("path", sshKeyPath)
	}

	// 创建SSH配置
	sshConfig := fmt.Sprintf(`Host *
    StrictHostKeyChecking no
    IdentityFile %s
    UserKnownHostsFile /dev/null`, sshKeyPath)

	// 保存SSH配置
	configDir := filepath.Join(workspaceDir, ".ssh")
	if err := os.MkdirAll(configDir, 0700); err != nil {
		return errors.NewErrorWithCode(errors.ErrCodeIO, "创建SSH配置目录失败").
			WithContext("dir", configDir)
	}

	configPath := filepath.Join(configDir, "config")
	if err := os.WriteFile(configPath, []byte(sshConfig), 0600); err != nil {
		return errors.NewErrorWithCode(errors.ErrCodeIO, "写入SSH配置失败").
			WithContext("path", configPath)
	}

	c.sshConfig = configPath
	return nil
}

// 克隆代码
func (c *Client) Clone(ctx context.Context, repo, branch string) error {
	// 准备工作目录
	if err := c.prepareWorkDir(); err != nil {
		return errors.WithContext(err, "repo", repo)
	}

	// 配置SSH
	if err := c.configureSSH(); err != nil {
		return errors.WithContext(err, "repo", repo)
	}

	// 设置Git命令环境变量
	env := append(os.Environ(),
		fmt.Sprintf("GIT_SSH_COMMAND=ssh -F %s", c.sshConfig),
	)

	// 构建克隆命令
	cmd := exec.CommandContext(ctx, "git", "clone",
		"--depth", "1", // 浅克隆
		"--single-branch",  // 只克隆单个分支
		"--branch", branch, // 指定分支
		"--progress", // 显示进度
		repo,         // 仓库地址
		c.workDir,    // 目标目录
	)
	cmd.Env = env

	// 执行克隆命令
	c.log.Info("克隆代码: %s 分支: %s", repo, branch)
	c.log.Debug("工作目录: %s", c.workDir)

	output, err := cmd.CombinedOutput()
	if err != nil {
		return errors.NewErrorWithCode(errors.ErrCodeGitClone, fmt.Sprintf("克隆失败: %s", string(output))).
			WithContext("repo", repo).
			WithContext("branch", branch)
	}

	// 验证克隆结果
	if err := c.verifyClone(); err != nil {
		return errors.WithContext(err, "repo", repo)
	}

	c.log.Info("代码克隆完成")
	return nil
}

// 验证克隆结果
func (c *Client) verifyClone() error {
	// 检查.git目录
	gitDir := filepath.Join(c.workDir, ".git")
	if _, err := os.Stat(gitDir); os.IsNotExist(err) {
		return errors.NewErrorWithCode(errors.ErrCodeGitClone, ".git目录不存在")
	}

	// 检查当前分支
	cmd := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD")
	cmd.Dir = c.workDir
	output, err := cmd.CombinedOutput()
	if err != nil {
		return errors.NewErrorWithCode(errors.ErrCodeGitCheckout, "无法获取当前分支").
			WithContext("output", string(output))
	}

	c.log.Debug("当前分支: %s", string(output))
	return nil
}

// 清理工作目录
func (c *Client) Clean() error {
	if c.workDir == "" {
		return nil
	}

	c.log.Debug("清理工作目录: %s", c.workDir)
	if err := os.RemoveAll(c.workDir); err != nil {
		return errors.NewErrorWithCode(errors.ErrCodeIO, "清理工作目录失败").
			WithContext("dir", c.workDir)
	}

	return nil
}
